#define _MSC_VER 1300
#include <conio.h>
#include <stdlib.h>
#include <al.h>
#include <alc.h>
//#include <alu.h>
#include <AL/alut.h>
#include <iostream>
#include <math.h>
#include <vector>
#include <stdio.h>
using namespace std;


// These index the buffers.

#define THUNDER     0
#define WATERDROP   1
#define STREAM      2
#define RAIN        3
#define CHIMES      4
#define OCEAN       5
#define NUM_BUFFERS 6



// Buffers hold sound data.
ALuint Buffers[NUM_BUFFERS];

// A vector list of sources for multiple emissions.
vector<ALuint> Sources;



// Position of the source sounds.
ALfloat SourcePos[] = { 0.0, 0.0, 0.0 };

// Velocity of the source sounds.
ALfloat SourceVel[] = { 0.0, 0.0, 0.0 };



// Position of the listener.
ALfloat ListenerPos[] = { 0.0, 0.0, 0.0 };

// Velocity of the listener.
ALfloat ListenerVel[] = { 0.0, 0.0, 0.0 };

// Orientation of the listener. (first 3 elements are "at", second 3 are "up")
ALfloat ListenerOri[] = { 0.0, 0.0, -1.0, 0.0, 1.0, 0.0 };




/*
 * ALboolean InitOpenAL()
 *
 *  Gets a handle to the sound device, and obtains a context to stream
 *  audio samples through.
 */
ALboolean InitOpenAL()
{
    ALCdevice* pDevice;
    ALCcontext* pContext;
    ALCubyte* defaultDevice;

    // Get handle to device.

    pDevice = alcOpenDevice("DirectSound3D");

    // Get the device specifier.

    //defaultDevice = alcGetString(pDevice, ALC_DEVICE_SPECIFIER);

    //printf("Using device '%s'.\n", defaultDevice);

    // Create audio context.

    pContext = alcCreateContext(pDevice, NULL);

    // Set active context.

    alcMakeContextCurrent(pContext);

    // Do an error check.

    if(alcGetError(pDevice) != ALC_NO_ERROR)
        return AL_FALSE;

    alGetError(); // Clear the bit.

    return AL_TRUE;
}



/*
 * void ExitOpenAL()
 *
 *  This will release all contexts and devices in use.
 */
void ExitOpenAL()
{
    ALCcontext* pCurContext;
    ALCdevice* pCurDevice;

    // Get the current context.

    pCurContext = alcGetCurrentContext();

    // Get the device used by that context.

    pCurDevice = alcGetContextsDevice(pCurContext);

    // Reset the current context to NULL.

    alcMakeContextCurrent(NULL);

    // Release the context and the device.

    alcDestroyContext(pCurContext);
    alcCloseDevice(pCurDevice);
}



/*
 * ALboolean LoadALData()
 *
 *	This function will load our sample data from the disk using the alut
 *	utility and send the data into OpenAL as a buffer. A source is then
 *	also created to play that buffer.
 */
ALboolean LoadALData()
{
	// Variables to load into.

	ALenum format;
	ALsizei size;
	ALvoid* data;
	ALsizei freq;
	ALboolean loop;

	// Load wav data into buffers.

	alGenBuffers(NUM_BUFFERS, Buffers);

	if(alGetError() != AL_NO_ERROR)
		return AL_FALSE;

	alutLoadWAVFile("wavdata/thunder.wav", &format, &data, &size, &freq, &loop);
	alBufferData(Buffers[THUNDER], format, data, size, freq);
	alutUnloadWAV(format, data, size, freq);

	alutLoadWAVFile("wavdata/waterdrop.wav", &format, &data, &size, &freq, &loop);
	alBufferData(Buffers[WATERDROP], format, data, size, freq);
	alutUnloadWAV(format, data, size, freq);

	alutLoadWAVFile("wavdata/stream.wav", &format, &data, &size, &freq, &loop);
	alBufferData(Buffers[STREAM], format, data, size, freq);
	alutUnloadWAV(format, data, size, freq);

	alutLoadWAVFile("wavdata/rain.wav", &format, &data, &size, &freq, &loop);
	alBufferData(Buffers[RAIN], format, data, size, freq);
	alutUnloadWAV(format, data, size, freq);

	alutLoadWAVFile("wavdata/ocean.wav", &format, &data, &size, &freq, &loop);
	alBufferData(Buffers[OCEAN], format, data, size, freq);
	alutUnloadWAV(format, data, size, freq);

	alutLoadWAVFile("wavdata/chimes.wav", &format, &data, &size, &freq, &loop);
	alBufferData(Buffers[CHIMES], format, data, size, freq);
	alutUnloadWAV(format, data, size, freq);

	// Do another error check and return.

	if(alGetError() != AL_NO_ERROR)
		return AL_FALSE;

	return AL_TRUE;
}



/*
 * void AddSource(ALint type)
 *
 *  Will add a new water drop source to the audio scene.
 */
void AddSource(ALint type)
{
	ALuint Source;

	alGenSources(1, &Source);

	if(alGetError() != AL_NO_ERROR)
	{
		printf("Error generating audio source.");
		exit(-1);
	}

	alSourcei (Source, AL_BUFFER,   Buffers[type]);
	alSourcef (Source, AL_PITCH,    1.0f         );
	alSourcef (Source, AL_GAIN,     1.0f         );
	alSourcefv(Source, AL_POSITION, SourcePos    );
	alSourcefv(Source, AL_VELOCITY, SourceVel    );
	alSourcei (Source, AL_LOOPING,  AL_TRUE      );

	alSourcePlay(Source);

	Sources.push_back(Source);
}



/*
 * void SetListenerValues()
 *
 *	We already defined certain values for the listener, but we need
 *	to tell OpenAL to use that data. This function does just that.
 */
void SetListenerValues()
{
	alListenerfv(AL_POSITION,    ListenerPos);
	alListenerfv(AL_VELOCITY,    ListenerVel);
	alListenerfv(AL_ORIENTATION, ListenerOri);
}



/*
 * void KillALData()
 *
 *	We have allocated memory for our buffers and sources which needs
 *	to be returned to the system. This function frees that memory.
 */
void KillALData()
{
	for(vector<ALuint>::iterator iter = Sources.begin(); iter != Sources.end(); iter++)
		alDeleteSources(1, &(*iter));
	Sources.clear();
	alDeleteBuffers(NUM_BUFFERS, Buffers);
	ExitOpenAL();
}



int main(int argc, char *argv[])
{
	// Initialize OpenAL.

	if(InitOpenAL() == AL_FALSE)
	{
		printf("Could not initialize.");
		getche();
		return 0;
	}

	printf("MindCode's OpenAL Lesson 5: Sources Sharing Buffers\n\n");
	printf("Controls:\n");
	printf("w) Water drops.\n");
	printf("t) Rolling thunder.\n");
	printf("s) Stream of trickling water.\n");
	printf("r) Rain.\n");
	printf("o) Lapping ocean waves.\n");
	printf("c) Wind chimes.\n");
	printf("q) Quit program.\n");

	// Load the wav data.

	if(LoadALData() == AL_FALSE)
		return 0;

	SetListenerValues();

	// Setup an exit procedure.

	atexit(KillALData);

	// Loop.

	ALubyte c = ' ';

	while(c != 'q')
	{
		cin>>c;

		switch(c)
		{
			case 'w': AddSource(WATERDROP); break;
			case 't': AddSource(THUNDER);   break;
			case 's': AddSource(STREAM);    break;
			case 'r': AddSource(RAIN);      break;
			case 'o': AddSource(OCEAN);     break;
			case 'c': AddSource(CHIMES);    break;
		};
	}

	return 0;
}
